import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;

public class EventManager
{
    private IOProgram IOProgram = new IOProgram();
    private LoadSaveManager LoadSaveManager = new LoadSaveManager();
    private ObjectStorage ObjectStorage = new ObjectStorage();
    private Settings Settings = new Settings();

    private ReentrantLock TryProcessUserMouseActionAntiJamLock = new ReentrantLock();

    private ArrayList<DrawObjectEventProcessor> DrawObjectEventProcessors = null;

    private boolean ChangesExisting(MainWindow.GUIElements GUIElements, LoadSaveDataEx LoadSaveDataEx, LoadSaveManager LoadSaveManager)
    {
        String File1Path = LoadSaveManager.GetOverridePath();

        if (File1Path == null || (File1Path.length() == 0))
            return false;

        String File2Path = "./NCIDE_file_compare.tmp";

        // do exactly the same way as if the user saved the file:
        @SuppressWarnings("unused")
        LoadSaveDataEx LoadSaveDataExTemp = ObjectStorage.SaveAllToFile(File2Path, new LoadSaveDataEx(true, "", GUIElements.GetIOPanel().GetIOProgramString(), ZoomManager.GetZoomFactor()));

        boolean FileHashCodesEqual = CompareFileHashCodes(File1Path, File2Path);

        return !(FileHashCodesEqual);
    }

    public boolean CheckForChangesExisting(MainWindow.GUIElements GUIElements, KeyEventProcessor KeyEventProcessor, MouseEventProcessor MouseEventProcessor)
    {
        BeforeProcessingUserAction(GUIElements, KeyEventProcessor, MouseEventProcessor);

        GUIElements.GetMainWindow().GetTitleBarInfo().AddInfoText("PLEASE WAIT", false, true, true, true);

        boolean ChangesExisting = ChangesExisting(GUIElements, new LoadSaveDataEx(true, "", GUIElements.GetIOPanel().GetIOProgramString(), ZoomManager.GetZoomFactor()), LoadSaveManager);

        GUIElements.GetMainWindow().GetTitleBarInfo().RemoveInfoText("PLEASE WAIT");

        AfterProcessingUserAction(GUIElements, KeyEventProcessor, MouseEventProcessor);

        return ChangesExisting;
    }

    private boolean CompareFileHashCodes(String File1Path, String File2Path)
    { // returns true if equal, false if not

        long HashCode1 = LoadSaveManager.GetFileHashCode(File1Path);
        long HashCode2 = LoadSaveManager.GetFileHashCode(File2Path);

        return (HashCode1 == HashCode2);
    }

    public EventManager()
    {
        InitializeDrawObjectEventProcessors();
    }

    public IOProgram GetIOProgram()
    {
        return IOProgram;
    }

    public LoadSaveManager GetLoadSaveManager()
    {
        return LoadSaveManager;
    }

    public ObjectStorage GetObjectStorage()
    {
        return ObjectStorage;
    }

    public Settings GetSettings()
    {
        return Settings;
    }

    private void InitializeDrawObjectEventProcessors()
    {
        DrawObjectEventProcessors = new ArrayList<DrawObjectEventProcessor>();

        DrawObjectEventProcessors.add(new GeneralEventProcessor()); // call this first!
        DrawObjectEventProcessors.add(new SelectionEventProcessor()); // call this second!
        DrawObjectEventProcessors.add(new CommentFieldEventProcessor());
        DrawObjectEventProcessors.add(new FKeyEventProcessor(GetIOProgram(), GetLoadSaveManager(), GetObjectStorage(), GetSettings()));
        DrawObjectEventProcessors.add(new GridEventProcessor());
        DrawObjectEventProcessors.add(new LineEventProcessor());
        DrawObjectEventProcessors.add(new NeuronEventProcessor());
        DrawObjectEventProcessors.add(new NodeEventProcessor());
        DrawObjectEventProcessors.add(new SampleAndHoldEventProcessor());
        DrawObjectEventProcessors.add(new TextFieldEventProcessor());
        DrawObjectEventProcessors.add(new VisualFieldEventProcessor());
        DrawObjectEventProcessors.add(new CADUEventProcessor());
    }

    @SuppressWarnings("unused")
    private boolean NOT(boolean Value)
    {
        return !Value;
    }

    private int BeforeAfterProcessingUserActionCounter = 0;

    private void BeforeProcessingUserAction(MainWindow.GUIElements GUIElements, KeyEventProcessor KeyEventProcessor, MouseEventProcessor MouseEventProcessor)
    {
        if (BeforeAfterProcessingUserActionCounter < 0)
            BeforeAfterProcessingUserActionCounter = 0; // just to make sure

        if (BeforeAfterProcessingUserActionCounter == 0)
        {
            if (IOProgram.IsExecuted())
            {
                GUIElements.GetMainWindow().GetTitleBarInfo().AddInfoText("PAUSED", false, true, false, false);

                IOProgram.PauseExecution();
            }

            // not useful (looks a bit ugly), COPIED and PASTED messages are shown anyway:
            // GUIElements.GetMainWindow().GetTitleBarInfo().AddInfoText(PatienceMessage, false, true, true, true); // will show up only if processing input takes a longer time (e.g. moving millions of objects)

            GUIElements.GetMainWindow().DisableRedraw();
        }
        BeforeAfterProcessingUserActionCounter++;
    }

    private void AfterProcessingUserAction(MainWindow.GUIElements GUIElements, KeyEventProcessor KeyEventProcessor, MouseEventProcessor MouseEventProcessor)
    {
        BeforeAfterProcessingUserActionCounter--;

        if (BeforeAfterProcessingUserActionCounter < 0)
            BeforeAfterProcessingUserActionCounter = 0; // just to make sure

        if (BeforeAfterProcessingUserActionCounter == 0)
        {
            // GUIElements.GetMainWindow().GetTitleBarInfo().RemoveInfoText(PatienceMessage); // disabled, see above

            if (IOProgram.IsExecuted())
            {
                IOProgram.ResumeExecution();

                GUIElements.GetMainWindow().GetTitleBarInfo().RemoveInfoText("PAUSED");
            }
            else
            {
                GUIElements.GetMainWindow().GetTitleBarInfo().RemoveInfoText("PAUSED");
            }

            if (GUIElements.GetMainWindow().IsHelpEnabled())
                GUIElements.GetMainWindow().UpdateHelpText(); // update statistics

            // update title bar messages (TitleBarInfo checks (fast) for each call if already existing)...

            GUIElements.GetMainWindow().GetTitleBarInfo().RemoveInfoTextContaining("F4 saves as");
            if (LoadSaveManager.GetOverrideFileName() != null)
            {
                GUIElements.GetMainWindow().GetTitleBarInfo().AddInfoText("F4 saves as " + LoadSaveManager.GetOverrideFileName(), false, false, false, false);
            }

            GUIElements.GetMainWindow().EnableRedraw();
        }
    }

    public void TryProcessUserKeyboardAction(MainWindow.GUIElements GUIElements, KeyEventProcessor KeyEventProcessor, MouseEventProcessor MouseEventProcessor)
    {
        BeforeProcessingUserAction(GUIElements, KeyEventProcessor, MouseEventProcessor);

        if (KeyEventProcessor.AnyKeyPressed() ||
            KeyEventProcessor.AnyKeyTyped())
        {
            for (int m = 0; m < DrawObjectEventProcessors.size(); m++)
                DrawObjectEventProcessors.get(m).TryProcessUserKeyboardAction(GUIElements, KeyEventProcessor, MouseEventProcessor, ObjectStorage, IOProgram);
        }

        AfterProcessingUserAction(GUIElements, KeyEventProcessor, MouseEventProcessor);
    }

    public void TryProcessUserMouseAction(MainWindow.GUIElements GUIElements, KeyEventProcessor KeyEventProcessor, MouseEventProcessor MouseEventProcessor)
    {
        BeforeProcessingUserAction(GUIElements, KeyEventProcessor, MouseEventProcessor);

        if (TryProcessUserMouseActionAntiJamLock.tryLock()) // works CLEARLY better with this, in tests, there were no problems with "lost" mouse events, instead, the system worked smoother
        {
            for (int m = 0; m < DrawObjectEventProcessors.size(); m++)
                DrawObjectEventProcessors.get(m).TryProcessUserMouseAction(GUIElements, KeyEventProcessor, MouseEventProcessor, ObjectStorage, IOProgram);
            TryProcessUserMouseActionAntiJamLock.unlock();
        }

        AfterProcessingUserAction(GUIElements, KeyEventProcessor, MouseEventProcessor);
    }
}
